//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-03-09     ZhangYihua   first version
//
// Description  : 
//################################################################################

//            /---<----\
//   in_dat   |        |       out_dat
// --------->(+)-(s)->[D]--(t)--------->
//                                 acc_ff
// acc = acc+in_dat; out_dat = truncate(acc);
module acc #(
parameter           IN_DW                   = 10,   // IN_DW-IN_FW<=OUT_DW-OUT_FW
parameter           IN_FW                   = 16,
parameter           OUT_DW                  = 12,
parameter           OUT_FW                  = 9     // must OUT_FW<=IN_FW
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input                                       in_vld,
input       signed  [IN_DW-1:0]             in_dat,     // s( IN_DW,  IN_FW)
output      signed  [OUT_DW-1:0]            out_dat,    // s(OUT_DW, OUT_FW)

input                                       cfg_acc_clr,    // clear acc to ini
input                                       cfg_acc_frz,    // freeze acc, but cfg_frz is lower priority than cfg_clr
input       signed  [OUT_DW-1:0]            cfg_acc_ini,    // s(OUT_DW, OUT_FW), initial value
output      signed  [OUT_DW-1:0]            sts_acc_rtv     // s(OUT_DW, OUT_FW), real-time value
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          OUT_IW                  = OUT_DW-OUT_FW;    // OUT_IW<=0 is legal
localparam          ACC_FW                  = IN_FW;
localparam          ACC_DW                  = OUT_IW+ACC_FW;

wire        signed  [ACC_DW+1-1:0]          acc_c;
wire        signed  [ACC_DW-1:0]            acc_s;
wire                                        acc_over_nc;
reg         signed  [ACC_DW-1:0]            cfg_ini_e;
reg         signed  [ACC_DW-1:0]            acc_ff;
wire                                        out_over_nc;
//################################################################################
// main
//################################################################################

assign acc_c = acc_ff + in_dat;

s_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (ACC_DW+1                       ),	// input data width
        .IFW                            (ACC_FW                         ),	// input fractional width
        .ODW                            (ACC_DW                         ),	// output data width
        .OFW                            (ACC_FW                         ),	// output fractional width
        .TRU_MODE                       ("FLOOR"                        )	// discade fractional bits directly for less area and higher Fmax
) u_acc_sat ( 
        .id                             (acc_c                          ),	// s(IDW, IFW), the MSB is sign
        .od                             (acc_s                          ),	// s(ODW, OFW), the MSB is sign
        .over                           (acc_over_nc                    )
);

always@(*) begin
    cfg_ini_e = {ACC_DW{1'b0}};
    cfg_ini_e[ACC_DW-1-:OUT_DW] = cfg_acc_ini;
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        acc_ff <=`U_DLY {ACC_DW{1'b0}};
    end else if (cke==1'b1) begin
        if (cfg_acc_clr==1'b1)
            acc_ff <=`U_DLY cfg_ini_e;
        else if ((in_vld==1'b1) && (cfg_acc_frz==1'b0))
            acc_ff <=`U_DLY acc_s;
        else
            ;
    end else
        ;
end

s_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (ACC_DW                         ),	// input data width
        .IFW                            (ACC_FW                         ),	// input fractional width
        .ODW                            (OUT_DW                         ),	// output data width
        .OFW                            (OUT_FW                         ),	// output fractional width
        .TRU_MODE                       ("FLOOR"                        )	// discade fractional bits directly for less area and higher Fmax
) u_out_tru ( 
        .id                             (acc_ff                         ),	// s(IDW, IFW), the MSB is sign
        .od                             (out_dat                        ),	// s(ODW, OFW), the MSB is sign
        .over                           (out_over_nc                    )
);

assign sts_acc_rtv = out_dat;

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

initial begin
    if (OUT_FW>IN_FW) begin
        $error("out fractional width is bigger than input fractional width.");
        $stop;
    end

    if (IN_DW-IN_FW>OUT_DW-OUT_FW) begin
        $error("input integer width is bigger than output integer width.");
        $stop;
    end
end

// synopsys translate_on
`endif

endmodule
